using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;

namespace MiddlewareClient.Socket
{
    class SocketClient
    {
        public delegate void ReadDelegate(byte[] data);
        private ReadDelegate _readDelegate;

        private TcpClient _tcp;
        private BinaryReader _reader;
        private BinaryWriter _writer;
        private NetworkStream _netStream;
        private Thread _thread;

        private const int _msgLenght = 8;
        private byte[] _data = new byte[_msgLenght];

        public SocketClient()
        {
        }

        public SocketClient(ReadDelegate readDelegate)
        {
            Init(readDelegate);
        }

        public void Init(ReadDelegate readDelegate)
        {
            _readDelegate += readDelegate;
        }

        public void Connect(string host, int port)
        {
            try
            {
                _tcp = new TcpClient(host, port);
                _netStream = _tcp.GetStream();
                _reader = new BinaryReader(_netStream);
                _writer = new BinaryWriter(_netStream);

                _thread = new Thread(new ThreadStart(Run));
                _thread.Start();
            }
            catch (Exception ex)
            {
                Console.Out.WriteLine(ex);
            }
        }

        public void Disconnect()
        {
            try
            {
                _tcp.Client.Shutdown(SocketShutdown.Both);
                _tcp.Client.Disconnect(false);
                _tcp.GetStream().Close();
                _tcp.Close();
            }
            catch (Exception ex)
            {
            }

        }

        private void Run()
        {
            Console.WriteLine("[SC]::Run");
            try
            {
                while (_tcp.Connected)
                {
                    _reader.Read(_data, 0, _msgLenght);

                    //Interpret header
                    HeaderStruct hdr = BytesToStruct(_data);
                    Console.Out.WriteLine("HDR TO: {0} {1}", hdr.msgType, hdr.msgLength);

                    //Prepare for rest of structure with data:
                    byte[] packetData = new byte[hdr.msgLength];

                    //Waiting for real pocket data
                    _reader.Read(packetData, 0, hdr.msgLength - _msgLenght);

                    MemoryStream ms = new MemoryStream(new byte[hdr.msgLength], 0, hdr.msgLength, true, true);
                    ms.Write(_data, 0, _msgLenght);
                    ms.Write(packetData, 0, hdr.msgLength - _msgLenght);
                    byte[] mergedData = ms.GetBuffer();

                    if (_readDelegate != null)
                    {
                        _readDelegate(mergedData);
                    }
                    else
                        Console.Out.WriteLine("delegate is empty");
                }
            }
            catch (Exception ex)
            {
                Console.Out.WriteLine(ex);
            }

            Console.Out.WriteLine("client thread ended");
        }

        public void Write(byte[] data)
        {
            try
            {
                _writer.BaseStream.Write(data, 0, data.Length);
                _writer.BaseStream.Flush();
            }
            catch (Exception ex)
            {
                Console.Out.WriteLine(ex);
            }
        }


        public HeaderStruct BytesToStruct(byte[] dataIn)
        {
            GCHandle hDataIn = GCHandle.Alloc(dataIn, GCHandleType.Pinned);
            HeaderStruct ys = (HeaderStruct)Marshal.PtrToStructure(hDataIn.AddrOfPinnedObject(),
            typeof(HeaderStruct));
            hDataIn.Free();
            return ys;
        }
    }
}
